#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <dirent.h>
#include <getopt.h>
#include <unistd.h>

static int strict_cnt_flag = 0;        // 严格统计标记,也就是不加上代码文件中的空行,当其为1时有效


/*判断字符串str是否以字符串end_str结尾*/
int StringEndWith(char * str, char *end_str){
    if(str == NULL || end_str == NULL) {
        return 0;
    }

    int i, j;
    int len_str     = strlen(str);
    int len_end_str = strlen(end_str);
    
    if(len_str < len_end_str){
        return 0;
    }

    for(i = len_end_str - 1, j = len_str - 1; i >= 0 && j >= 0; --i, --j){
        if(str[j] != end_str[i]){
            return 0;
        }
    }
    return 1;
}
/*判断一行字符是否全部由空格组成*/
int Legal_Line(char *cur_line_code,int len){
    if(cur_line_code == NULL){
        return 0;
    }
    for(int i = 0; i < len;++i){
        if(cur_line_code[i] != '\r' && cur_line_code[i] != ' ' && cur_line_code[i] != '\n'){
            return 1;
        }
    }
    return 0;
}

struct dirent * ReadNextValidDir(DIR* dir){

    int flag = 0;
    if(dir == NULL) 
        return NULL;

    struct dirent* file = NULL;
    while( (file = readdir(dir)) != NULL){
        switch(file->d_type){
            case DT_DIR:
                if(!strcmp(file->d_name,"..") || !strcmp(file->d_name,".")){
                    continue;
                }
                flag = 1;
                break;
            case DT_REG:
                if(!StringEndWith(file->d_name,".c") && !StringEndWith(file->d_name,".h") ){
                    continue;
                }
                flag = 1;
                break;
            default :
                continue;
        }
        if(flag){
            break;
        }
    }
    return file;
}


int Code_Lines_In_Dir(char *dir, char *print_buff){

    int pre_line_of_your_code;
    int line_of_your_code = 0;
    int last_file_dir_flag = 0;
    /*打开文件夹*/
    DIR *open_dir = opendir(dir);
    if(open_dir == NULL){
        printf("\033[31m你输入的文件夹路径:\"%s\"有误,请重新运行程序!\n",dir);
        exit(2);
    }


    /*读取文件夹中的文件,并且统计行数*/
    struct dirent *nxt_file_entry = NULL;
    struct dirent *cur_file_entry = NULL;   // 获取文件夹下的文件入口描述结构
    cur_file_entry = ReadNextValidDir(open_dir);

    if(cur_file_entry == NULL){
        nxt_file_entry = cur_file_entry;
    }else{
        nxt_file_entry = ReadNextValidDir(open_dir);
        if(nxt_file_entry == NULL){
            nxt_file_entry = cur_file_entry;
            last_file_dir_flag = 1;
        }
    }

do_last:
    while(nxt_file_entry != NULL){
        char tmp_dir_name[1024];
        char file_name[1024];
        int len_dir = strlen(dir);
        FILE *cur_file = NULL;
        

         
        switch(cur_file_entry->d_type){
            /*文件夹*/
            case DT_DIR:
            strcpy(tmp_dir_name,dir);
            if(dir[len_dir-1] != '/'){
                tmp_dir_name[len_dir] = '/';
                strcpy(tmp_dir_name + len_dir + 1,cur_file_entry->d_name);
            }else{
                strcpy(tmp_dir_name + len_dir,cur_file_entry->d_name);
            }   
            
            printf("\033[0m%s",print_buff);
            char *child = (char*)malloc(sizeof(char) * 256);
            strcpy(child,print_buff);
            if(last_file_dir_flag){
                printf("\033[0m└── \033[34m%s\n",cur_file_entry->d_name);
                strcat(child,"    \0");
            }else{
                printf("\033[0m├── \033[34m%s\n",cur_file_entry->d_name);
                strcat(child,"|   \0");
            }
            line_of_your_code += Code_Lines_In_Dir(tmp_dir_name,child);
            free(child);
            break;

            /*普通文件.c .h*/
            case DT_REG:
            strcpy(file_name,dir);
            if(dir[len_dir-1] != '/'){
                file_name[len_dir] = '/';
                strcpy(file_name + len_dir + 1,cur_file_entry->d_name);
            }else{
                strcpy(file_name + len_dir,cur_file_entry->d_name);
            }

            cur_file = fopen(file_name,"r");
            if(cur_file == NULL){
                printf("\033[31m文件: %s 打开失败!!!\n",file_name);
                cur_file_entry = nxt_file_entry;     // 读取下一个文件
                nxt_file_entry = ReadNextValidDir(open_dir);
            }  

            /*统计有效行数,若某一行只有空格,则不算一行代码*/
            pre_line_of_your_code = line_of_your_code;
            char cur_line_code[1024];
            char *fgets_flag = NULL;
            while((fgets_flag = fgets(cur_line_code,1024,cur_file)) != NULL){
                if(!strict_cnt_flag || Legal_Line(cur_line_code,strlen(cur_line_code))){
                    line_of_your_code++;
                }
            }
            fclose(cur_file);
            printf("\033[0m%s",print_buff);
            if(last_file_dir_flag){
                printf("\033[0m└── %-*s <--> %d lines \n",28,cur_file_entry->d_name,line_of_your_code - pre_line_of_your_code);
            }else{
                printf("\033[0m├── %-*s <--> %d lines \n",28,cur_file_entry->d_name,line_of_your_code - pre_line_of_your_code);
            }
            break;

            default :
            break;
        }
        
        cur_file_entry = nxt_file_entry;     // 读取下一个文件
        nxt_file_entry = ReadNextValidDir(open_dir);
    }

    if(nxt_file_entry == NULL && cur_file_entry != NULL && !last_file_dir_flag){
        nxt_file_entry = cur_file_entry;
        last_file_dir_flag = 1;
        goto do_last;
    }

    closedir(open_dir);
    return line_of_your_code;
}


int main(int argc, char *argv[])
{
    int line_of_your_code = 0;      // 你写过的代码行数       
    int free_flag = 0;
 
    char *code_path = NULL;         // 要统计文件所在的目录
    /*处理命令行参数*/
    const char *opt_string = "d:sh";
    int opt;
    while((opt = getopt(argc,argv,opt_string)) != EOF){
        switch (opt)
        {
        case 'd':
            code_path = optarg;     // 使用指定的文件夹
            break;
        case 's':
            strict_cnt_flag = 1;    // 启用严格统计
            break;
        case 'h':
            printf("\033[32m使用选项 -d + 文件夹名 可以统计该文件夹下的所有.c .h文件中的代码行数,默认统计的是当前文件夹下的代码\n");
            printf("\033[32m使用选项 -s 可以启用严格统计(也就是不统计文件中的空行)\n");
            exit(0);
            break;
        default:
            printf("\033[32m%c :无效的选项!,使用-h选项运行以获得帮助\n",opt);
            exit(3);
            break;
        }
    }
    if(code_path == NULL){          // 使用默认文件夹,就是当前cntCode所在的文件夹
        int malloc_tst_time = 1;    // 内存申请次数
        free_flag = 1;
        while( (code_path = (char*)malloc(sizeof(char) * 3) ) == NULL && ++malloc_tst_time <= 10){
            printf("\033[31m默认文件夹(./)创建失败！正在次尝试第%*d次创建...\n",2,malloc_tst_time);
        }
        strcpy(code_path,"./");
    }
    while(*code_path == ' ') code_path++;
    printf("\033[34m%s\n",code_path);
    char *print_buff = (char *)malloc(256 * sizeof(char));
    strcpy(print_buff,"");
    line_of_your_code = Code_Lines_In_Dir(code_path,print_buff);
    printf("\033[32m一共[%-*d]行代码\n\n\n",8,line_of_your_code);


    if(free_flag) 
        free(code_path);
    free(print_buff);
    return 0;
}